﻿
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using Wolfpack.Contrib.Checks.Powershell.Scripting;
using Wolfpack.Core;
using Wolfpack.Core.Checks;
using System.Linq;
using Wolfpack.Core.Interfaces.Entities;
using Omu.ValueInjecter;
using Wolfpack.Core.Notification;

namespace Wolfpack.Contrib.Checks.Powershell
{
    public class RunPowershellScript : HealthCheckBase<RunPowershellScriptConfig>
    {
        private IRunspaceConfigurationFactory _runspaceConfigurationFactory;

        public RunPowershellScript(RunPowershellScriptConfig config)
            : base(config)
        {
            config.SnapIns = config.SnapIns ?? new List<string>();
            config.Parameters = config.Parameters ?? new Dictionary<string, string>();
        }

        protected override PluginDescriptor BuildIdentity()
        {
            return new PluginDescriptor
            {
                Description = "This check will run a powershell scipt; it can optionally return health check results.",
                Name = "RunPowershellScript",
                TypeId = new Guid("94E1F4C8-5FEA-4AB4-B58B-FD2A19166BB6")
            };
        }

        public override void Initialise()
        {
            _runspaceConfigurationFactory = new CachingRunspaceConfigurationFactory(_config.SnapIns);
        }

        public override void Execute()
        {
            var runspaceConfig = _runspaceConfigurationFactory.Create();

            using (var runspace = RunspaceFactory.CreateRunspace(runspaceConfig))
            {
                runspace.Open();

                using (var pipeline = runspace.CreatePipeline())
                {
                    Logger.Info("\t[RunPowershellScript] running script {0}", _config.Script);
                    var scriptExecutionCommand = new Command(_config.Script, true);

                    // add the parameters to the script excution command
                    foreach (var parameter in _config.Parameters)
                    {
                        scriptExecutionCommand.Parameters.Add(parameter.Key, parameter.Value);
                        Logger.Debug("\t\t[RunPowershellScript] adding parameter...{0}:={1}", parameter.Key, parameter.Value);
                    }

                    // merge the errors stream to the output stream
                    scriptExecutionCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
                    pipeline.Commands.Add(scriptExecutionCommand);
                    var psObjects = pipeline.Invoke();

                    ProcessResults(psObjects);
                }
            }
        }

        protected virtual void ProcessResults(Collection<PSObject> psObjects)
        {
            var i = 0;
            psObjects.ToList().ForEach(
                result =>
                    {
                        try
                        {
                            if (HandleFailure(result, i))
                            {
                                return;
                            }

                            // it worked...process the return...
                            Logger.Debug("\t[RunPowershellScript] Processing result[{0}]...", i);

                            if (HandleHealthCheckResult(result, i))
                            {
                                return;
                            }

                            Logger.Debug("\t[RunPowershellScript] Result[{0}]:={1}", i, result);
                        }
                        finally
                        {
                            i++;
                        }
                    });
        }

        protected virtual bool HandleFailure(PSObject result, int index)
        {
            var baseObject = result.BaseObject;
            if (baseObject != null && baseObject is ErrorRecord)
            {
                // handle failure
                var error = (ErrorRecord) baseObject;
                Logger.Info("\t[RunPowershellScript] Error[{0}]:={1}", index, error);

                // let wolfpack infrastructure handle this...
                throw error.Exception;
            }

            return false;
        }

        protected virtual bool HandleHealthCheckResult(PSObject result, int index)
        {
            var psData = result.BaseObject as PSHealthCheckData;

            if (psData == null)
            {
                return false;
            }

            Logger.Debug("\t[RunPowershellScript] {0} returned a PSHealthCheckData object...", _config.Script);

            // map PS data to native WP data object
            var data = HealthCheckData.For(Identity, "Powershell script '{0}' result", _config.Script);
            data.InjectFrom<LoopValueInjection>(psData);
            
            if (psData.Identity != null)
                data.Identity.InjectFrom<LoopValueInjection>(psData.Identity);
            if (psData.Geo != null)
                data.Geo.InjectFrom<LoopValueInjection>(psData.Geo);
            if (psData.CriticalFailureDetails != null)
                data.CriticalFailureDetails.InjectFrom<LoopValueInjection>(psData.CriticalFailureDetails);
            if (psData.Properties != null)
            {
                psData.Properties.ToList().ForEach(p => data.AddProperty(p.Key, p.Value));
            }

            Publish(NotificationRequestBuilder.For(_config.NotificationMode, data).Build());
            return true;
        }
    }
}